//
// System.Drawing.Image.cs
//
// Authors: 	Christian Meyer (Christian.Meyer@cs.tum.edu)
// 		Alexandre Pigolkine (pigolkine@gmx.de)
//		Jordi Mas i Hernandez (jordi@ximian.com)
//		Sanjay Gupta (gsanjay@novell.com)
//		Ravindra (rkumar@novell.com)
//		Sebastien Pouliot  <sebastien@ximian.com>
//
// Copyright (C) 2002 Ximian, Inc.  http://www.ximian.com
// Copyright (C) 2004, 2007 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;

namespace System.Drawing
{
[Serializable]
[ComVisible (true)]
#if XNET
public abstract class Image : MarshalByRefObject
{
#else
[Editor ("System.Drawing.Design.ImageEditor, " + Consts.AssemblySystem_Drawing_Design, typeof (System.Drawing.Design.UITypeEditor))]
[TypeConverter (typeof(ImageConverter))]
[ImmutableObject (true)]
public abstract class Image : MarshalByRefObject, IDisposable , ICloneable, ISerializable 
{
	public delegate bool GetThumbnailImageAbort();
#if NET_2_0	
	private object tag;
#endif	
	
	internal IntPtr nativeObject = IntPtr.Zero;
	// when using MS GDI+ and IStream we must ensure the stream stays alive for all the life of the Image
	// http://groups.google.com/group/microsoft.public.win32.programmer.gdi/browse_thread/thread/4967097db1469a27/4d36385b83532126?lnk=st&q=IStream+gdi&rnum=3&hl=en#4d36385b83532126
	internal Stream stream;
	
	
	// constructor
	internal  Image()
	{	
	}
	
	internal Image (SerializationInfo info, StreamingContext context)
	{
		foreach (SerializationEntry serEnum in info) {
			if (String.Compare(serEnum.Name, "Data", true) == 0) {
				byte[] bytes = (byte[]) serEnum.Value;

				if (bytes != null) {
					MemoryStream ms = new MemoryStream (bytes);
					nativeObject = InitFromStream (ms);
					// under Win32 stream is owned by SD/GDI+ code
					if (GDIPlus.RunningOnWindows ())
						stream = ms;
				}
			}
		}
	}

	// FIXME - find out how metafiles (another decoder-only codec) are handled
	void ISerializable.GetObjectData (SerializationInfo si, StreamingContext context)
	{
		using (MemoryStream ms = new MemoryStream ()) {
			// Icon is a decoder-only codec
			if (RawFormat.Equals (ImageFormat.Icon)) {
				Save (ms, ImageFormat.Png);
			} else {
				Save (ms, RawFormat);
			}
			si.AddValue ("Data", ms.ToArray ());
		}
	}

	// public methods
	// static
	public static Image FromFile(string filename)
	{
		return FromFile (filename, false);
	}
	
	public static Image FromFile(string filename, bool useEmbeddedColorManagement)
	{
		IntPtr imagePtr;
		Status st;

		if (!File.Exists (filename))
			throw new FileNotFoundException (filename);

		if (useEmbeddedColorManagement)
			st = GDIPlus.GdipLoadImageFromFileICM (filename, out imagePtr);
		else
			st = GDIPlus.GdipLoadImageFromFile (filename, out imagePtr);
		GDIPlus.CheckStatus (st);

		return CreateFromHandle (imagePtr);
	}

	public static Bitmap FromHbitmap(IntPtr hbitmap)
	{
		return FromHbitmap (hbitmap, IntPtr.Zero);
	}

	public static Bitmap FromHbitmap(IntPtr hbitmap, IntPtr hpalette)
	{		
		IntPtr imagePtr;
		Status st;

		st = GDIPlus.GdipCreateBitmapFromHBITMAP (hbitmap, hpalette, out imagePtr);

		GDIPlus.CheckStatus (st);
		return new Bitmap (imagePtr);
	}

	// note: FromStream can return either a Bitmap or Metafile instance

	public static Image FromStream (Stream stream)
	{
		return LoadFromStream (stream, false);
	}

	[MonoLimitation ("useEmbeddedColorManagement  isn't supported.")]
	public static Image FromStream (Stream stream, bool useEmbeddedColorManagement)
	{
		return LoadFromStream (stream, false);
	}

	// See http://support.microsoft.com/default.aspx?scid=kb;en-us;831419 for performance discussion	
	[MonoLimitation ("useEmbeddedColorManagement  and validateImageData aren't supported.")]
	public static Image FromStream (Stream stream, bool useEmbeddedColorManagement, bool validateImageData)
	{
		return LoadFromStream (stream, false);
	}

	internal static Image LoadFromStream (Stream stream, bool keepAlive)
	{
		if (stream == null)
			throw new ArgumentNullException ("stream");

		Image img = CreateFromHandle (InitFromStream (stream));

		// Under Windows, we may need to keep a reference on the stream as long as the image is alive
		// (GDI+ seems to use a lazy loader)
		if (keepAlive && GDIPlus.RunningOnWindows ())
			img.stream = stream;

		return img;
	}

	internal static Image CreateFromHandle (IntPtr handle)
	{
		ImageType type;
		GDIPlus.CheckStatus (GDIPlus.GdipGetImageType (handle, out type));
		switch (type) {
		case ImageType.Bitmap:
			return new Bitmap (handle);
		case ImageType.Metafile:
			return new Metafile (handle);
		default:
			throw new NotSupportedException (Locale.GetText ("Unknown image type."));
		}
	}

	public static int GetPixelFormatSize(PixelFormat pixfmt)
	{
		int result = 0;
		switch (pixfmt) {
			case PixelFormat.Format16bppArgb1555:
			case PixelFormat.Format16bppGrayScale:
			case PixelFormat.Format16bppRgb555:
			case PixelFormat.Format16bppRgb565:
				result = 16;
				break;
			case PixelFormat.Format1bppIndexed:
				result = 1;
				break;
			case PixelFormat.Format24bppRgb:
				result = 24;
				break;
			case PixelFormat.Format32bppArgb:
			case PixelFormat.Format32bppPArgb:
			case PixelFormat.Format32bppRgb:
				result = 32;
				break;
			case PixelFormat.Format48bppRgb:
				result = 48;
				break;
			case PixelFormat.Format4bppIndexed:
				result = 4;
				break;
			case PixelFormat.Format64bppArgb:
			case PixelFormat.Format64bppPArgb:
				result = 64;
				break;
			case PixelFormat.Format8bppIndexed:
				result = 8;
				break;
		}
		return result;
	}

	public static bool IsAlphaPixelFormat(PixelFormat pixfmt)
	{
		bool result = false;
		switch (pixfmt) {
			case PixelFormat.Format16bppArgb1555:
			case PixelFormat.Format32bppArgb:
			case PixelFormat.Format32bppPArgb:
			case PixelFormat.Format64bppArgb:
			case PixelFormat.Format64bppPArgb:
				result = true;
				break;
			case PixelFormat.Format16bppGrayScale:
			case PixelFormat.Format16bppRgb555:
			case PixelFormat.Format16bppRgb565:
			case PixelFormat.Format1bppIndexed:
			case PixelFormat.Format24bppRgb:
			case PixelFormat.Format32bppRgb:
			case PixelFormat.Format48bppRgb:
			case PixelFormat.Format4bppIndexed:
			case PixelFormat.Format8bppIndexed:
				result = false;
				break;
		}
		return result;
	}
	
	public static bool IsCanonicalPixelFormat (PixelFormat pixfmt)
	{
		return ((pixfmt & PixelFormat.Canonical) != 0);
	}
	
	public static bool IsExtendedPixelFormat (PixelFormat pixfmt)
	{
		return ((pixfmt & PixelFormat.Extended) != 0);
	}

	internal static IntPtr InitFromStream (Stream stream)
	{
		if (stream == null)
			throw new ArgumentException ("stream");

		IntPtr imagePtr;
		Status st;
		
		// Seeking required
		if (!stream.CanSeek) {
			byte[] buffer = new byte[256];
			int index = 0;
			int count;

			do {
				if (buffer.Length < index + 256) {
					byte[] newBuffer = new byte[buffer.Length * 2];
					Array.Copy(buffer, newBuffer, buffer.Length);
					buffer = newBuffer;
				}
				count = stream.Read(buffer, index, 256);
				index += count;
			}
			while (count != 0);

			stream = new MemoryStream(buffer, 0, index);
		}

		if (GDIPlus.RunningOnUnix ()) {
			// Unix, with libgdiplus
			// We use a custom API for this, because there's no easy way
			// to get the Stream down to libgdiplus.  So, we wrap the stream
			// with a set of delegates.
			GDIPlus.GdiPlusStreamHelper sh = new GDIPlus.GdiPlusStreamHelper (stream, true);

			st = GDIPlus.GdipLoadImageFromDelegate_linux (sh.GetHeaderDelegate, sh.GetBytesDelegate,
				sh.PutBytesDelegate, sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, out imagePtr);
		} else {
			st = GDIPlus.GdipLoadImageFromStream (new ComIStreamWrapper (stream), out imagePtr);
		}

		return st == Status.Ok ? imagePtr : IntPtr.Zero;
	}

	// non-static	
	public RectangleF GetBounds (ref GraphicsUnit pageUnit)
	{	
		RectangleF source;			
		
		Status status = GDIPlus.GdipGetImageBounds (nativeObject, out source, ref pageUnit);
		GDIPlus.CheckStatus (status);		
		
		return source;
	}
	
	public EncoderParameters GetEncoderParameterList(Guid encoder)
	{
		Status status;
		uint sz;

		status = GDIPlus.GdipGetEncoderParameterListSize (nativeObject, ref encoder, out sz);
		GDIPlus.CheckStatus (status);

		IntPtr rawEPList = Marshal.AllocHGlobal ((int) sz);
		EncoderParameters eps;

		try {
			status = GDIPlus.GdipGetEncoderParameterList (nativeObject, ref encoder, sz, rawEPList);
			eps = EncoderParameters.FromNativePtr (rawEPList);
			GDIPlus.CheckStatus (status);
		}
		finally {
			Marshal.FreeHGlobal (rawEPList);
		}

		return eps;
	}
	
	public int GetFrameCount (FrameDimension dimension)
	{
		uint count;
		Guid guid = dimension.Guid;

		Status status = GDIPlus.GdipImageGetFrameCount (nativeObject, ref guid, out count); 
		GDIPlus.CheckStatus (status);
		
		return (int) count;
	}
	
	public PropertyItem GetPropertyItem(int propid)
	{
		int propSize;
		IntPtr property;
		PropertyItem item = new PropertyItem ();
		GdipPropertyItem gdipProperty = new GdipPropertyItem ();
		Status status;
			
		status = GDIPlus.GdipGetPropertyItemSize (nativeObject, propid, 
									out propSize);
		GDIPlus.CheckStatus (status);

		/* Get PropertyItem */
		property = Marshal.AllocHGlobal (propSize);
		try {
			status = GDIPlus.GdipGetPropertyItem (nativeObject, propid, propSize, property);
			GDIPlus.CheckStatus (status);
			gdipProperty = (GdipPropertyItem) Marshal.PtrToStructure (property, 
								typeof (GdipPropertyItem));						
			GdipPropertyItem.MarshalTo (gdipProperty, item);
		}
		finally {
			Marshal.FreeHGlobal (property);
		}
		return item;
	}
	
	public Image GetThumbnailImage (int thumbWidth, int thumbHeight, Image.GetThumbnailImageAbort callback, IntPtr callbackData)
	{
		if ((thumbWidth <= 0) || (thumbHeight <= 0))
			throw new OutOfMemoryException ("Invalid thumbnail size");

		Image ThumbNail = new Bitmap (thumbWidth, thumbHeight);

		using (Graphics g = Graphics.FromImage (ThumbNail)) {
			Status status = GDIPlus.GdipDrawImageRectRectI (g.nativeObject, nativeObject,
				0, 0, thumbWidth, thumbHeight,
				0, 0, this.Width, this.Height,
				GraphicsUnit.Pixel, IntPtr.Zero, null, IntPtr.Zero);

			GDIPlus.CheckStatus (status);
		}

		return ThumbNail;
	}
	
	
	public void RemovePropertyItem (int propid)
	{		
		Status status = GDIPlus.GdipRemovePropertyItem (nativeObject, propid);
		GDIPlus.CheckStatus (status);					
	}	
	
	public void RotateFlip (RotateFlipType rotateFlipType)
	{			
		Status status = GDIPlus.GdipImageRotateFlip (nativeObject, rotateFlipType);
		GDIPlus.CheckStatus (status);				
	}

	internal ImageCodecInfo findEncoderForFormat (ImageFormat format)
	{
		ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();			
		ImageCodecInfo encoder = null;
		
		if (format.Guid.Equals (ImageFormat.MemoryBmp.Guid))
			format = ImageFormat.Png;
	
		/* Look for the right encoder for our format*/
		for (int i = 0; i < encoders.Length; i++) {
			if (encoders[i].FormatID.Equals (format.Guid)) {
				encoder = encoders[i];
				break;
			}			
		}

		return encoder;
	}

	public void Save (string filename)
	{
		Save (filename, RawFormat);
	}

	public void Save(string filename, ImageFormat format) 
	{
		ImageCodecInfo encoder = findEncoderForFormat (format);
		if (encoder == null) {
			// second chance
			encoder = findEncoderForFormat (RawFormat);
			if (encoder == null) {
				string msg = Locale.GetText ("No codec available for saving format '{0}'.", format.Guid);
				throw new ArgumentException (msg, "format");
			}
		}
		Save (filename, encoder, null);
	}

	public void Save(string filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
	{
		Status st;
		Guid guid = encoder.Clsid;

		if (encoderParams == null) {
			st = GDIPlus.GdipSaveImageToFile (nativeObject, filename, ref guid, IntPtr.Zero);
		} else {
			IntPtr nativeEncoderParams = encoderParams.ToNativePtr ();
			st = GDIPlus.GdipSaveImageToFile (nativeObject, filename, ref guid, nativeEncoderParams);
			Marshal.FreeHGlobal (nativeEncoderParams);
		}

		GDIPlus.CheckStatus (st);
	}

	public void Save (Stream stream, ImageFormat format)
	{
		ImageCodecInfo encoder = findEncoderForFormat (format);

		if (encoder == null)
			throw new ArgumentException ("No codec available for format:" + format.Guid);

		Save (stream, encoder, null);
	}

	public void Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams)
	{
		Status st;
		IntPtr nativeEncoderParams;
		Guid guid = encoder.Clsid;

		if (encoderParams == null)
			nativeEncoderParams = IntPtr.Zero;
		else
			nativeEncoderParams = encoderParams.ToNativePtr ();

		try {
			if (GDIPlus.RunningOnUnix ()) {
				GDIPlus.GdiPlusStreamHelper sh = new GDIPlus.GdiPlusStreamHelper (stream, false);
				st = GDIPlus.GdipSaveImageToDelegate_linux (nativeObject, sh.GetBytesDelegate, sh.PutBytesDelegate,
					sh.SeekDelegate, sh.CloseDelegate, sh.SizeDelegate, ref guid, nativeEncoderParams);
			} else {
				st = GDIPlus.GdipSaveImageToStream (new HandleRef (this, nativeObject), 
					new ComIStreamWrapper (stream), ref guid, new HandleRef (encoderParams, nativeEncoderParams));
			}
		}
		finally {
			if (nativeEncoderParams != IntPtr.Zero)
				Marshal.FreeHGlobal (nativeEncoderParams);
		}
		
		GDIPlus.CheckStatus (st);		
	}
	
	public void SaveAdd (EncoderParameters encoderParams)
	{
		Status st;
		
		IntPtr nativeEncoderParams = encoderParams.ToNativePtr ();
		st = GDIPlus.GdipSaveAdd (nativeObject, nativeEncoderParams);
		Marshal.FreeHGlobal (nativeEncoderParams);
		GDIPlus.CheckStatus (st);
	}
		
	public void SaveAdd (Image image, EncoderParameters encoderParams)
	{
		Status st;
		
		IntPtr nativeEncoderParams = encoderParams.ToNativePtr ();
		st = GDIPlus.GdipSaveAddImage (nativeObject, image.NativeObject, nativeEncoderParams);
		Marshal.FreeHGlobal (nativeEncoderParams);
		GDIPlus.CheckStatus (st);
	}
		
	public int SelectActiveFrame(FrameDimension dimension, int frameIndex)
	{
		Guid guid = dimension.Guid;		
		Status st = GDIPlus.GdipImageSelectActiveFrame (nativeObject, ref guid, frameIndex);
		
		GDIPlus.CheckStatus (st);			
		
		return frameIndex;		
	}

	public void SetPropertyItem(PropertyItem propitem)
	{
		throw new NotImplementedException ();
/*
		GdipPropertyItem pi = new GdipPropertyItem ();
		GdipPropertyItem.MarshalTo (pi, propitem);
		unsafe {
			Status status = GDIPlus.GdipSetPropertyItem (nativeObject, &pi);
			
			GDIPlus.CheckStatus (status);
		}
*/
	}

	// properties	
	[Browsable (false)]
	public int Flags {
		get {
			int flags;
			
			Status status = GDIPlus.GdipGetImageFlags (nativeObject, out flags);			
			GDIPlus.CheckStatus (status);						
			return flags;			
		}
	}
	
	[Browsable (false)]
	public Guid[] FrameDimensionsList {
		get {
			uint found;
			Status status = GDIPlus.GdipImageGetFrameDimensionsCount (nativeObject, out found);
			GDIPlus.CheckStatus (status);
			Guid [] guid = new Guid [found];
			status = GDIPlus.GdipImageGetFrameDimensionsList (nativeObject, guid, found);
			GDIPlus.CheckStatus (status);  
			return guid;
		}
	}

	[DefaultValue (false)]
	[Browsable (false)]
	[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
	public int Height {
		get {
			uint height;			
			Status status = GDIPlus.GdipGetImageHeight (nativeObject, out height);		
			GDIPlus.CheckStatus (status);			
			
			return (int)height;
		}
	}
	
	public float HorizontalResolution {
		get {
			float resolution;
			
			Status status = GDIPlus.GdipGetImageHorizontalResolution (nativeObject, out resolution);			
			GDIPlus.CheckStatus (status);			
			
			return resolution;
		}
	}
	
	[Browsable (false)]
	public ColorPalette Palette {
		get {							
			return retrieveGDIPalette();
		}
		set {
			storeGDIPalette(value);
		}
	}

	internal ColorPalette retrieveGDIPalette()
	{
		int bytes;
		ColorPalette ret = new ColorPalette ();

		Status st = GDIPlus.GdipGetImagePaletteSize (nativeObject, out bytes);
		GDIPlus.CheckStatus (st);
		IntPtr palette_data = Marshal.AllocHGlobal (bytes);
		try {
			st = GDIPlus.GdipGetImagePalette (nativeObject, palette_data, bytes);
			GDIPlus.CheckStatus (st);
			ret.setFromGDIPalette (palette_data);
			return ret;
		}

		finally {
			Marshal.FreeHGlobal (palette_data);
		}
	}

	internal void storeGDIPalette(ColorPalette palette)
	{
		if (palette == null) {
			throw new ArgumentNullException("palette");
		}
		IntPtr palette_data = palette.getGDIPalette();
		if (palette_data == IntPtr.Zero) {
			return;
		}

		try {
			Status st = GDIPlus.GdipSetImagePalette (nativeObject, palette_data);
			GDIPlus.CheckStatus (st);
		}

		finally {
			Marshal.FreeHGlobal(palette_data);
		}
	}

		
	public SizeF PhysicalDimension {
		get {
			float width,  height;
			Status status = GDIPlus.GdipGetImageDimension (nativeObject, out width, out height);		
			GDIPlus.CheckStatus (status);			
			
			return new SizeF (width, height);
		}
	}
	
	public PixelFormat PixelFormat {
		get {			
			PixelFormat pixFormat;				
			Status status = GDIPlus.GdipGetImagePixelFormat (nativeObject, out pixFormat);		
			GDIPlus.CheckStatus (status);			
			
			return pixFormat;
		}		
	}
	
	[Browsable (false)]
	public int[] PropertyIdList {
		get {
			uint propNumbers;
			
			Status status = GDIPlus.GdipGetPropertyCount (nativeObject, 
									out propNumbers);			
			GDIPlus.CheckStatus (status);
			
			int [] idList = new int [propNumbers];
			status = GDIPlus.GdipGetPropertyIdList (nativeObject, 
								propNumbers, idList);
			GDIPlus.CheckStatus (status);
			
			return idList;
		}
	}
	
	[Browsable (false)]
	public PropertyItem[] PropertyItems {
		get {
			int propNums, propsSize, propSize;
			IntPtr properties, propPtr;
			PropertyItem[] items;
			GdipPropertyItem gdipProperty = new GdipPropertyItem ();
			Status status;
			
			status = GDIPlus.GdipGetPropertySize (nativeObject, out propsSize, out propNums);
			GDIPlus.CheckStatus (status);

			items =  new PropertyItem [propNums];
			
			if (propNums == 0)
				return items;			
					
			/* Get PropertyItem list*/
			properties = Marshal.AllocHGlobal (propsSize * propNums);
			try {
				status = GDIPlus.GdipGetAllPropertyItems (nativeObject, propsSize, 
								propNums, properties);
				GDIPlus.CheckStatus (status);

				propSize = Marshal.SizeOf (gdipProperty);			
				propPtr = properties;
			
				for (int i = 0; i < propNums; i++, propPtr = new IntPtr (propPtr.ToInt64 () + propSize)) {
					gdipProperty = (GdipPropertyItem) Marshal.PtrToStructure 
						(propPtr, typeof (GdipPropertyItem));						
					items [i] = new PropertyItem ();
					GdipPropertyItem.MarshalTo (gdipProperty, items [i]);								
				}
			}
			finally {
				Marshal.FreeHGlobal (properties);
			}
			return items;
		}
	}

	public ImageFormat RawFormat {
		get {
			Guid guid;
			Status st = GDIPlus.GdipGetImageRawFormat (nativeObject, out guid);
			
			GDIPlus.CheckStatus (st);
			return new ImageFormat (guid);			
		}
	}
	
	public Size Size {
		get {
			return new Size(Width, Height);
		}
	}

#if NET_2_0
	[DefaultValue (null)]
	[LocalizableAttribute(false)] 
	[BindableAttribute(true)] 	
	[TypeConverter (typeof (StringConverter))]
	public object Tag { 
		get { return tag; }
		set { tag = value; }
	}
#endif	
	public float VerticalResolution {
		get {
			float resolution;
			
			Status status = GDIPlus.GdipGetImageVerticalResolution (nativeObject, out resolution);
			GDIPlus.CheckStatus (status);

			return resolution;
		}
	}

	[DefaultValue (false)]
	[Browsable (false)]
	[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
	public int Width {
		get {
			uint width;			
			Status status = GDIPlus.GdipGetImageWidth (nativeObject, out width);		
			GDIPlus.CheckStatus (status);			
			
			return (int)width;
		}
	}
	
	internal IntPtr NativeObject{
		get{
			return nativeObject;
		}
		set	{
			nativeObject = value;
		}
	}
	
	public void Dispose ()
	{
		Dispose (true);
		GC.SuppressFinalize (this);
	}

	~Image ()
	{
		Dispose (false);
	}

	protected virtual void Dispose (bool disposing)
	{
		if (GDIPlus.GdiPlusToken != 0 && nativeObject != IntPtr.Zero) {
			Status status = GDIPlus.GdipDisposeImage (nativeObject);
			// dispose the stream (set under Win32 only if SD owns the stream) and ...
			if (stream != null) {
				stream.Close ();
				stream = null;
			}
			// ... set nativeObject to null before (possibly) throwing an exception
			nativeObject = IntPtr.Zero;
			GDIPlus.CheckStatus (status);		
		}
	}
	
	public object Clone ()
	{
		if (GDIPlus.RunningOnWindows () && stream != null)
			return CloneFromStream ();

		IntPtr newimage = IntPtr.Zero;
		Status status = GDIPlus.GdipCloneImage (NativeObject, out newimage);
		GDIPlus.CheckStatus (status);

		if (this is Bitmap)
			return new Bitmap (newimage);
		else
			return new Metafile (newimage);
	}

	// On win32, when cloning images that were originally created from a stream, we need to
	// clone both the image and the stream to make sure the gc doesn't kill it
	// (when using MS GDI+ and IStream we must ensure the stream stays alive for all the life of the Image)
	object CloneFromStream ()
	{
		byte[] bytes = new byte [stream.Length];
		MemoryStream ms = new MemoryStream (bytes);
		int count = (stream.Length < 4096 ? (int) stream.Length : 4096);
		byte[] buffer = new byte[count];
		stream.Position = 0;
		do {
			count = stream.Read (buffer, 0, count);
			ms.Write (buffer, 0, count);
		} while (count == 4096);

		IntPtr newimage = IntPtr.Zero;
		newimage = InitFromStream (ms);

		if (this is Bitmap)
			return new Bitmap (newimage, ms);
		else
			return new Metafile (newimage, ms);
	}
#endif
}

}
